<?php
declare(strict_types=1);

namespace Zms\Calculation;

use Exception;
use Zms\Calculation\Traits\Common;


/**
 * @describe 逆波兰算法出现问题太多，改为四则运算配合高精度计算函数,匹配计算模式
 * @user zlf zms-pro@qq.com 2025/1/10
 */
class Calculation
{
    use Common;


    /**
     * @describe 开始计算
     * @return float
     * @throws Exception
     * @user zlf zms-pro@qq.com 2025/1/10
     */
    private function startCount()
    {
        $this->handerSymbols();//处理算式
        $this->validityCheckOfEquations();//算式合法性检查
        $formula = $this->formula;
        while (preg_match('/(\()/', $formula)) {
            $formula = $this->startMinimumOperation($formula);//颗粒化运算
        }
        return (float)$this->fourFundamentalOperations($formula);//最后进行一次四则运算
    }


    /**
     * @describe 处理算式
     * @return void
     * @user zlf zms-pro@qq.com 2025/1/10
     */
    public function handerSymbols()
    {
        //去除算式中的空白字符
        $this->formula = str_replace(' ', '', $this->formula);
        $this->formula = preg_replace('/(\d+)\s*\(/', '$1*(', $this->formula);//隐式乘法转为显示乘法
        $this->formula = preg_replace('/(--)/', '~-', $this->formula);//减去未加括号的负数处理
        //把-转为~,避免符号无法识别
        $this->formula = preg_replace('/(\d+)-(\d+)/', '$1~$2', $this->formula);
        $this->formula = preg_replace('/\)-/', ')~', $this->formula);
        $this->formula = preg_replace('/-\(/', '~(', $this->formula);
    }


    /**
     * @describe 开始最小运算
     * @param string $formula
     * @return string
     * @user zlf zms-pro@qq.com 2025/1/10
     */
    public function startMinimumOperation($formula)
    {
        $prev_symbol = '';//上一个符号
        $match_start = 0;//上一次左括号出现的位置

        //匹配最小运算单位，不用正则，不太会容易把握不住
        for ($i = 0; $i < strlen($formula); $i++) {
            if ($formula[$i] == '(') {//开始括号
                $prev_symbol = '(';
                $match_start = $i;
            } elseif ($formula[$i] == ')') {//结束括号
                if ($prev_symbol === '(') {//获取到最近一次的开启符号,则与上一次闭合
                    $minimum_formula = substr($formula, $match_start, $i - $match_start + 1);//最小运算
                    $result = $this->fourFundamentalOperations(trim($minimum_formula, '()'));
                    return str_replace($minimum_formula, $result, $formula);
                }
            }
        }
        return $formula;
    }


    /**
     * @describe 四则运算
     * @param $formula
     * @return string
     * @user zlf zms-pro@qq.com 2025/1/10
     */
    private function fourFundamentalOperations($formula)
    {
        $formulas = $this->explode($formula);
        //计算乘除
        $rideIndex = array_search('*', $formulas);//乘号索引
        $divideIndex = array_search('/', $formulas);//除号索引
        while ($rideIndex !== false || $divideIndex !== false) {
            $rideIndex = $rideIndex === false ? 100000 : $rideIndex;
            $divideIndex = $divideIndex === false ? 100000 : $divideIndex;
            if ($rideIndex < $divideIndex) {//如果先出现乘法
                $num1 = $formulas[$rideIndex - 1];
                $num2 = $formulas[$rideIndex + 1];
                unset($formulas[$rideIndex - 1]);
                unset($formulas[$rideIndex + 1]);
                if (function_exists('bcmul')) {
                    $formulas[$rideIndex] = bcmul((string)$num1, (string)$num2, 10);
                } else {
                    $formulas[$rideIndex] = floatval($num1) * floatval($num2);
                }

            } else {//如果先出现除法
                $num1 = $formulas[$divideIndex - 1];
                $num2 = $formulas[$divideIndex + 1];
                unset($formulas[$divideIndex - 1]);
                unset($formulas[$divideIndex + 1]);
                if (function_exists('bcdiv')) {
                    $formulas[$divideIndex] = bcdiv((string)$num1, (string)$num2, 10);
                } else {
                    $formulas[$divideIndex] = floatval($num1) / floatval($num2);
                }
            }
            $formulas = array_values($formulas);
            $rideIndex = array_search('*', $formulas);//乘号索引
            $divideIndex = array_search('/', $formulas);//除号索引
        }


        //计算加减
        $addIndex = array_search('+', $formulas);//乘号索引
        $subIndex = array_search('~', $formulas);//除号索引
        while ($addIndex !== false || $subIndex !== false) {
            $addIndex = $addIndex === false ? 100000 : $addIndex;
            $subIndex = $subIndex === false ? 100000 : $subIndex;
            if ($addIndex < $subIndex) {//如果先出现乘法
                $num1 = $formulas[$addIndex - 1];
                $num2 = $formulas[$addIndex + 1];
                unset($formulas[$addIndex - 1]);
                unset($formulas[$addIndex + 1]);
                if (function_exists('bcadd')) {
                    $formulas[$addIndex] = bcadd((string)$num1, (string)$num2, 10);
                } else {
                    $formulas[$addIndex] = floatval($num1) + floatval($num2);
                }
            } else {//如果先出现除法
                $num1 = $formulas[$subIndex - 1];
                $num2 = $formulas[$subIndex + 1];
                unset($formulas[$subIndex - 1]);
                unset($formulas[$subIndex + 1]);
                if (function_exists('bcsub')) {
                    $formulas[$subIndex] = bcsub((string)$num1, (string)$num2, 10);
                } else {
                    $formulas[$subIndex] = floatval($num1) - floatval($num2);
                }
            }
            $formulas = array_values($formulas);
            $addIndex = array_search('+', $formulas);//乘号索引
            $subIndex = array_search('~', $formulas);//除号索引
        }
        return implode('', $formulas);
    }


    /**
     * @describe 分割算式出
     * @param string $formula
     * @return array
     * @user zlf zms-pro@qq.com 2025/1/10
     */
    private function explode(string $formula)
    {
        $elements = [];
        $prev = '';
        for ($i = 0; $i < strlen($formula); $i++) {
            if (in_array($formula[$i], ['+', '~', '*', '/'])) {
                $elements[] = $prev;
                $elements[] = $formula[$i];
                $prev = '';
            } else {
                $prev .= $formula[$i];
            }
        }
        if (strlen($prev) > 0) $elements[] = $prev;
        return $elements;
    }
}